/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

// 创建一个对象来存储延迟操作  
const delayedOperations = {};  
  
// 模拟 setTimeout 的函数  
function mySetTimeout(callback, delay) {  
  // 返回一个 Promise，该 Promise 在指定的延迟后解析  
  return new Promise(resolve => {  
    // 使用 Date.now() 和一个循环来模拟延迟  
    const startTime = Date.now();  
    const operationId = Symbol(); // 使用 Symbol 生成唯一的操作ID  
  
    // 将操作ID和解析函数存储在对象中  
    delayedOperations[operationId] = {  
      resolve,  
      startTime,  
      delay,  
    };  
  
    // 使用一个循环来模拟延迟  
    (function loop() {  
      const now = Date.now();  
      const elapsed = now - startTime;  
        
      // 如果延迟时间已过，解析 Promise 并移除操作  
      if (elapsed >= delay) {  
        resolve();  
        delete delayedOperations[operationId];  
      } else {  
        // 否则，在一段时间后再次检查  
        setTimeout(loop, 10); // 注意：这里仍然使用了 setTimeout，但仅用于循环检查  
      }  
    })();  
  }).then(callback); // 当 Promise 解析时，调用回调函数  
}  
  
// 模拟 clearTimeout 的函数  
function myClearTimeout(operationId) {  
  // 如果操作存在，则取消它  
  if (delayedOperations[operationId]) {  
    delete delayedOperations[operationId];  
  }  
}  


function Cache () {
  var _cache = Object.create(null);
  var _hitCount = 0;
  var _missCount = 0;
  var _size = 0;
  var _debug = false;

  this.put = function(key, value, time, timeoutCallback) {
    if (_debug) {
    //   console.log('caching: %s = %j (@%s)', key, value, time);
      // print('caching: %s = %j (@%s)', key, value, time);
    }

    if (typeof time !== 'undefined' && (typeof time !== 'number' || isNaN(time) || time <= 0)) {
      throw new Error('Cache timeout must be a positive number');
    } else if (typeof timeoutCallback !== 'undefined' && typeof timeoutCallback !== 'function') {
      throw new Error('Cache timeout callback must be a function');
    }

    var oldRecord = _cache[key];
    if (oldRecord) {
    //   clearTimeout(oldRecord.timeout);
    myClearTimeout(oldRecord.timeout)
    } else {
      _size++;
    }

    var record = {
      value: value,
      expire: time + Date.now()
    };

    if (!isNaN(record.expire)) {
    //   record.timeout = setTimeout(function() {
    //     _del(key);
    //     if (timeoutCallback) {
    //       timeoutCallback(key, value);
    //     }
    //   }.bind(this), time);
      record.timeout = mySetTimeout(function(){
        _del(key);
        if (timeoutCallback) {
          timeoutCallback(key, value);
        }
      }.bind(this), time); 
    }

    _cache[key] = record;

    return value;
  };

  this.del = function(key) {
    var canDelete = true;

    var oldRecord = _cache[key];
    if (oldRecord) {
    //   clearTimeout(oldRecord.timeout);
      myClearTimeout(oldRecord.timeout)
      if (!isNaN(oldRecord.expire) && oldRecord.expire < Date.now()) {
        canDelete = false;
      }
    } else {
      canDelete = false;
    }

    if (canDelete) {
      _del(key);
    }

    return canDelete;
  };

  function _del(key){
    _size--;
    delete _cache[key];
  }

  this.clear = function() {
    for (var key in _cache) {
    //   clearTimeout(_cache[key].timeout);
    myClearTimeout(_cache[key].timeout)
    }
    _size = 0;
    _cache = Object.create(null);
    if (_debug) {
      _hitCount = 0;
      _missCount = 0;
    }
  };

  this.get = function(key) {
    var data = _cache[key];
    if (typeof data != "undefined") {
      if (isNaN(data.expire) || data.expire >= Date.now()) {
        if (_debug) _hitCount++;
        return data.value;
      } else {
        // free some space
        if (_debug) _missCount++;
        _size--;
        delete _cache[key];
      }
    } else if (_debug) {
      _missCount++;
    }
    return null;
  };

  this.size = function() {
    return _size;
  };

  this.memsize = function() {
    var size = 0,
      key;
    for (key in _cache) {
      size++;
    }
    return size;
  };

  this.debug = function(bool) {
    _debug = bool;
  };

  this.hits = function() {
    return _hitCount;
  };

  this.misses = function() {
    return _missCount;
  };

  this.keys = function() {
    return Object.keys(_cache);
  };

  this.exportJson = function() {
    var plainJsCache = {};

    // Discard the `timeout` property.
    // Note: JSON doesn't support `NaN`, so convert it to `'NaN'`.
    for (var key in _cache) {
      var record = _cache[key];
      plainJsCache[key] = {
        value: record.value,
        expire: record.expire || 'NaN',
      };
    }

    return JSON.stringify(plainJsCache);
  };

  this.importJson = function(jsonToImport, options) {
    var cacheToImport = JSON.parse(jsonToImport);
    var currTime = Date.now();

    var skipDuplicates = options && options.skipDuplicates;

    for (var key in cacheToImport) {
      if (cacheToImport.hasOwnProperty(key)) {
        if (skipDuplicates) {
          var existingRecord = _cache[key];
          if (existingRecord) {
            if (_debug) {
            //   console.log('Skipping duplicate imported key \'%s\'', key);
            print('Skipping duplicate imported key \'%s\'', key);
            }
            continue;
          }
        }

        var record = cacheToImport[key];

        // record.expire could be `'NaN'` if no expiry was set.
        // Try to subtract from it; a string minus a number is `NaN`, which is perfectly fine here.
        var remainingTime = record.expire - currTime;

        if (remainingTime <= 0) {
          // Delete any record that might exist with the same key, since this key is expired.
          this.del(key);
          continue;
        }

        // Remaining time must now be either positive or `NaN`,
        // but `put` will throw an error if we try to give it `NaN`.
        remainingTime = remainingTime > 0 ? remainingTime : undefined;

        this.put(key, record.value, remainingTime);
      }
    }

    return this.size();
  };
}

// module.exports = new Cache();
// module.exports.Cache = Cache;
export default Cache;